package org.lex.sound;

import java.applet.AudioClip;
import java.io.File;
import java.io.InputStream;
import java.net.URL;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;

import org.lex.utils.StateChangeSupport;


/**
 * @author Daniel Liu
 */
public abstract class AudioPlayer extends StateChangeSupport implements Runnable, AudioClip {
	private static final int DEFAULT_BUFFER_SIZE = 5120;

	protected boolean playing = false;

	// static factory method
	public static AudioPlayer newPlayer(File file) {
		try {
			AudioInputStream ais = AudioSystem.getAudioInputStream(file);
			return new AudioInputStreamPlayer(ais);
		} catch (Exception e) {
			return null;
		}
	}

	public static AudioPlayer newPlayer(URL url) {
		try {
			AudioInputStream ais = AudioSystem.getAudioInputStream(url);
			return new AudioInputStreamPlayer(ais);
		} catch (Exception e) {
			return null;
		}
	}

	public static AudioPlayer newPlayer(InputStream is) {
		try {
			AudioInputStream ais = AudioSystem.getAudioInputStream(is);
			return new AudioInputStreamPlayer(ais);
		} catch (Exception e) {
			return null;
		}
	}

	// ===================
	public void play() {
		this.run();
	}

	public void stop() {
		this.setPlaying(false);
	}

	public void loop() {
		this.play();
	}

	public boolean isPlaying() {
		return this.playing;
	}

	protected void setPlaying(boolean playing) {
		this.playing = playing;
		this.fireStateChanged(this);
	}

	// concrete sub-class
	static class AudioInputStreamPlayer extends AudioPlayer {
		private AudioInputStream audioInputStream = null;
		private AudioFormat audioFormat = null;
		private SourceDataLine sourceDataLine = null;
		private int bufferSize = DEFAULT_BUFFER_SIZE;
		private byte buffer[] = null;

		public AudioInputStreamPlayer(AudioInputStream ais) {
			this(ais, DEFAULT_BUFFER_SIZE);
		}

		public AudioInputStreamPlayer(AudioInputStream ais, int bufferLength) {
			super();
			this.audioInputStream = ais;
			this.bufferSize = bufferLength;
		}

		public void run() {
			try {
				this.setPlaying(true);
				audioFormat = audioInputStream.getFormat();
				DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat);
				sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
				sourceDataLine.open(audioFormat);
				sourceDataLine.start();
				int count = 0;
				if (null == buffer)
					buffer = new byte[bufferSize];
				// Keep looping until the input read method
				// returns -1 for empty stream or the
				// user clicks the Stop button causing
				// stopPlayback to switch from false to
				// true.
				while (true == this.isPlaying() && (count = audioInputStream.read(buffer)) != -1) {
					if (count > 0) {
						// Write data to the internal buffer of
						// the data line where it will be
						// delivered to the speaker.
						sourceDataLine.write(buffer, 0, count);
					}
				}
				// Block and wait for internal buffer of the data line to empty.
				sourceDataLine.drain();
				sourceDataLine.close();
				// Prepare to playback another file
				this.setPlaying(false);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
